package com.small.nacos.core.datasource;

import com.alibaba.nacos.common.utils.ConvertUtils;
import com.small.nacos.config.server.utils.ConfigExecutor;
import com.small.nacos.sys.env.EnvUtil;
import com.small.nacos.sys.utils.IPUtil;
import com.zaxxer.hikari.HikariDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class ExternalDataSourceServiceImpl implements DataSourceService {

    protected static final Logger LOGGER = LoggerFactory.getLogger(ExternalDataSourceServiceImpl.class);

    /**
     * JDBC 执行超时时间（单位为秒）
     */
    private int queryTimeout = 3;

    /**
     * 设置事务最大超时时间
     */
    private static final int TRANSACTION_QUERY_TIMEOUT = 5;

    /**
     * jdbc模板
     */
    private JdbcTemplate jt;

    /**
     * 数据源管理器
     */
    private DataSourceTransactionManager tm;
    /**
     * 事务模板
     */
    private TransactionTemplate tjt;

    /**
     * 连接状态
     */
    private volatile List<Boolean> isHealthList;

    /**
     * Hikari数据源集合
     */
    private List<HikariDataSource> dataSourceList = new ArrayList<>();

    private JdbcTemplate testMasterJT;

    private volatile List<JdbcTemplate> testJtList;
    /**
     * 主连接数据源下标
     */
    private volatile int masterIndex;

    /**
     * 主服务器选择阀值
     */
    private static final int DB_MASTER_SELECT_THRESHOLD = 1;

    private static final String DB_LOAD_ERROR_MSG = "[db-load-error]load jdbc.properties error";

    public ExternalDataSourceServiceImpl() {

        try {
            //初始初始化数据源
            init();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    @Override
    public void init() throws Exception {
        queryTimeout = ConvertUtils.toInt(System.getProperty("QUERYTIMEOUT"), 3);
        jt = new JdbcTemplate();
        //设置最大记录数以防止内存溢出
        jt.setMaxRows(50000);
        //设置超时时间
        jt.setQueryTimeout(queryTimeout);
        //创建事务管理器
        tm = new DataSourceTransactionManager();
        //创建事务模板
        tjt = new TransactionTemplate(tm);
        //设置事务最大超时时间
        tjt.setTimeout(TRANSACTION_QUERY_TIMEOUT);


        testMasterJT = new JdbcTemplate();
        testMasterJT.setQueryTimeout(queryTimeout);
        testJtList = new ArrayList<JdbcTemplate>();
        isHealthList = new ArrayList<Boolean>();


        try {
            reload();
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(DB_LOAD_ERROR_MSG);
        }
        if (this.dataSourceList.size() > DB_MASTER_SELECT_THRESHOLD) {
            ConfigExecutor.scheduleConfigTask(new SelectMasterTask(), 10, 10, TimeUnit.SECONDS);
        }
        ConfigExecutor.scheduleConfigTask(new CheckDbHealthTask(), 10, 10, TimeUnit.SECONDS);

    }

    @Override
    public void reload() throws IOException {
        try {
            dataSourceList = new ExternalDataSourceProperties()
                    .build(EnvUtil.getEnvironment(), (dataSource) -> {
                        JdbcTemplate jdbcTemplate = new JdbcTemplate();
                        jdbcTemplate.setQueryTimeout(queryTimeout);
                        jdbcTemplate.setDataSource(dataSource);
                        testJtList.add(jdbcTemplate);
                        isHealthList.add(Boolean.TRUE);
                    });

            new SelectMasterTask().run();
            new CheckDbHealthTask().run();
        } catch (RuntimeException e) {
            LOGGER.error(DB_LOAD_ERROR_MSG, e);
            throw new IOException(e);
        }
    }

    @Override
    public JdbcTemplate getJdbcTemplate() {
        return this.jt;
    }

    @Override
    public TransactionTemplate getTransactionTemplate() {
        return this.tjt;
    }

    /**
     * 获取健康链接
     */
    class SelectMasterTask implements Runnable {

        @Override
        public void run() {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("check master db.");
            }
            boolean isFound = false;

            int index = -1;

            //测试数据源连接
            for (HikariDataSource ds : dataSourceList) {
                index++;
                testMasterJT.setDataSource(ds);
                testMasterJT.setQueryTimeout(queryTimeout);
                try {
                    testMasterJT.update("DELETE FROM config_info WHERE data_id='com.alibaba.nacos.testMasterDB'");
                    if (jt.getDataSource() != ds) {
                        LOGGER.warn("[master-db] {}", ds.getJdbcUrl());
                    }
                    jt.setDataSource(ds);
                    tm.setDataSource(ds);
                    isFound = true;
                    masterIndex = index;
                    break;
                } catch (DataAccessException e) {
                    e.printStackTrace();
                }
            }

            if (!isFound) {
                LOGGER.error("[master-db] master db not found.");
            }
        }
    }

    /**
     * 检查数据库链接是否健康
     */
    class CheckDbHealthTask implements Runnable {

        @Override
        public void run() {

            String sql = "SELECT * FROM config_info_beta WHERE id = 1";

            for (int i = 0; i < testJtList.size(); i++) {
                JdbcTemplate jdbcTemplate = testJtList.get(i);
                try {
                    jdbcTemplate.execute(sql);
                    isHealthList.set(i, Boolean.TRUE);
                } catch (DataAccessException e) {
                    if (i == masterIndex) {
                        LOGGER.error("[db-error] master db {} down.",
                                IPUtil.getIPFromString(dataSourceList.get(i).getJdbcUrl()));
                    } else {
                        LOGGER.error("[db-error] slave db {} down.",
                                IPUtil.getIPFromString(dataSourceList.get(i).getJdbcUrl()));
                    }
                    isHealthList.set(i, Boolean.FALSE);

                }
            }
        }
    }
}
